package com.wr.hangzhounewlight.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.f4b6a3.uuid.UuidCreator;
import com.wr.hangzhounewlight.mapper.OrderMapper;
import com.wr.hangzhounewlight.mapper.OutputMasterMapper;
import com.wr.hangzhounewlight.mapper.OutputSlaveMapper;
import com.wr.hangzhounewlight.mapper.SiloRawMaterialMapMapper;
import com.wr.hangzhounewlight.obj.database.OrderEntity;
import com.wr.hangzhounewlight.obj.database.OutputSlaveEntity;
import com.wr.hangzhounewlight.obj.database.SiloRawMaterialMapEntity;
import com.wr.hangzhounewlight.opcua.OpcUaClientManager;
import com.wr.hangzhounewlight.opcua.OpcUaUtil;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaMonitoredItem.DataValueListener;
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaSubscription;
import org.eclipse.milo.opcua.sdk.client.subscriptions.OpcUaSubscription.SubscriptionListener;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.IntStream;

/**
 * @author yuanzhizhuo
 * createDate 2025/11/3
 */
@Slf4j
@Service
public class WeighingListenerService {

    private final OpcUaClientManager opcUaClientManager;
    private final OrderMapper orderMapper;
    private final SiloRawMaterialMapMapper siloRawMaterialMapMapper;
    private final OutputMasterMapper outputMasterMapper;
    private final OutputSlaveMapper outputSlaveMapper;
    private final String opcUaUrl = "opc.tcp://frp-fog.com:48347";
    private static final Map<String, String> workshopMap = new HashMap<>();

    static {
        workshopMap.put("NongMo", "农膜");
        workshopMap.put("ChuiMo", "吹膜");
        workshopMap.put("BaoCai", "包材");
    }

    private static final Map<String, String> T = new HashMap<>();

    static {
        IntStream.rangeClosed(1, 60).forEach(i -> T.put("主料" + i, "BCRCP_" + String.format("%03d", i)));
    }

    public WeighingListenerService(
            OpcUaClientManager opcUaClientManager, OrderMapper orderMapper, SiloRawMaterialMapMapper siloRawMaterialMapMapper,
            OutputMasterMapper outputMasterMapper, OutputSlaveMapper outputSlaveMapper
    ) {
        this.opcUaClientManager = opcUaClientManager;
        this.orderMapper = orderMapper;
        this.siloRawMaterialMapMapper = siloRawMaterialMapMapper;
        this.outputMasterMapper = outputMasterMapper;
        this.outputSlaveMapper = outputSlaveMapper;
    }

    @PostConstruct
    private void addOpcUaNodeIdListener() {
        Optional<OpcUaClient> opcUaClientOptional = opcUaClientManager.getOpcUaClient(opcUaUrl);
        if (opcUaClientOptional.isPresent()) {
            OpcUaClient opcUaClient = opcUaClientOptional.get();
            try {
                List<NodeId> nodeIdList = OpcUaUtil.browseNode(opcUaClient, new NodeId(0, 53722));
                List<NodeId> listenerNodeIdList = getListenerNodeIdList(nodeIdList);
                List<DataValueListener> dataValueListenerList = Collections.nCopies(
                        listenerNodeIdList.size(), new MyDataValueListener(opcUaClient, orderMapper, siloRawMaterialMapMapper, outputMasterMapper, outputSlaveMapper)
                );
                OpcUaUtil.subscribeDataValue(opcUaClient, new MySubscriptionListener(), listenerNodeIdList, dataValueListenerList);
                log.info("配料量记录, 已添加监听点位: {}", listenerNodeIdList);
            } catch (ExecutionException | InterruptedException | TimeoutException | UaException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 过滤出所有需要监听的NodeId
     *
     * @param nodeIdList 所有节点的NodeId列表
     * @return 需要监听的NodeId列表
     */
    private static List<NodeId> getListenerNodeIdList(List<NodeId> nodeIdList) {
        return nodeIdList.stream().filter(nodeId -> String.valueOf(nodeId.getIdentifier()).contains("PLCRecord")).toList();
    }

    /**
     * 监听器级别回调
     */
    private static class MySubscriptionListener implements SubscriptionListener {
        @Override
        public void onDataReceived(OpcUaSubscription subscription, List<OpcUaMonitoredItem> items, List<DataValue> values) {
            for (int i = 0; i < items.size(); i++) {
                OpcUaMonitoredItem item = items.get(i);
                DataValue value = values.get(i);
                log.info("配料量记录, 监听器事件, 监听点位: {}发生变化, 新值: {}", item.getReadValueId().getNodeId(), value.getValue().getValue());
            }
        }
    }

    /**
     * 节点级别回调
     */
    private static class MyDataValueListener implements DataValueListener {

        private final OpcUaClient opcUaClient;
        private final OrderMapper orderMapper;
        private final SiloRawMaterialMapMapper siloRawMaterialMapMapper;
        private final OutputMasterMapper outputMasterMapper;
        private final OutputSlaveMapper outputSlaveMapper;
        private String workshop;
        private String workshopCH;
        private String machine;
        private String screw;

        public MyDataValueListener(
                OpcUaClient opcUaClient, OrderMapper orderMapper, SiloRawMaterialMapMapper siloRawMaterialMapMapper,
                OutputMasterMapper outputMasterMapper, OutputSlaveMapper outputSlaveMapper
        ) {
            this.opcUaClient = opcUaClient;
            this.orderMapper = orderMapper;
            this.siloRawMaterialMapMapper = siloRawMaterialMapMapper;
            this.outputMasterMapper = outputMasterMapper;
            this.outputSlaveMapper = outputSlaveMapper;
        }

        /**
         * 回调方法
         * <p>
         * 保存值到数据库
         */
        @Override
        public void onDataReceived(OpcUaMonitoredItem item, DataValue value) {
            int countValue = Integer.parseInt(String.valueOf(value.getValue().getValue()));
            if (countValue == 0) return;
            NodeId nodeId = item.getReadValueId().getNodeId();
            String[] identifierSplit = String.valueOf(nodeId.getIdentifier()).split("_");
            workshop = identifierSplit[0];
            workshopCH = workshopMap.get(workshop);
            machine = identifierSplit[1];
            screw = identifierSplit[2];
            String orderNo = readOrderNo();
            Integer count = readPLCRecord();
            String masterId = insertOutputMaster(orderNo, count);
            if (masterId == null) return;
            List<SiloRawMaterialMapEntity> siloRawMaterialMapList = selectSiloRawMaterialMap();
            List<String> siloList = siloRawMaterialMapList.stream().map(SiloRawMaterialMapEntity::getSilo).toList();
            List<Double> weightList = readBCRCP(siloList);
            for (int i = 0; i < siloRawMaterialMapList.size(); i++) {
                String silo = siloList.get(i);
                Double weight = weightList.get(i);
                if (weight == null) continue;
                OutputSlaveEntity outputSlaveEntity = new OutputSlaveEntity(masterId, silo, weight);
                outputSlaveMapper.insert(outputSlaveEntity);
            }
        }

        /**
         * 读取触发节点对应的组合单号
         *
         * @return 组合单号
         */
        private String readOrderNo() {
            String nodeIdentifier = workshop + "_" + machine + "_" + screw + "_curWO";
            try {
                Object value = opcUaClient.readValue(0.0, TimestampsToReturn.Both, new NodeId(1, nodeIdentifier)).getValue().getValue();
                return String.valueOf(value);
            } catch (UaException e) {
                log.error("配料量记录, 查询组合单号失败, 节点id: {}", nodeIdentifier, e);
                return null;
            }
        }

        /**
         * 读取触发节点对应的累积数
         *
         * @return 累积数
         */
        private Integer readPLCRecord() {
            String nodeIdentifier = workshop + "_" + machine + "_" + screw + "_PLCRecord";
            try {
                Object value = opcUaClient.readValue(0.0, TimestampsToReturn.Both, new NodeId(1, nodeIdentifier)).getValue().getValue();
                return Integer.parseInt(String.valueOf(value));
            } catch (UaException e) {
                log.error("查询累积值失败, 节点id: {}", nodeIdentifier, e);
                return 0;
            }
        }

        /**
         * 新增产出主表记录
         * <p>
         * 如果产出主表中已存在相同组合单号和产出计数的记录, 则忽略插入
         *
         * @param orderNo 组合单号
         * @param count   产出计数
         * @return 产出主表id
         */
        private String insertOutputMaster(String orderNo, int count) {
            LambdaQueryWrapper<OrderEntity> orderQueryWrapper = new LambdaQueryWrapper<OrderEntity>()
                    .select(OrderEntity::getId)
                    .eq(OrderEntity::getOrderNo, orderNo);
            OrderEntity orderEntity = orderMapper.selectOne(orderQueryWrapper);
            String masterId = UuidCreator.getTimeOrdered().toString();
            int insertIgnore = outputMasterMapper.insertIgnore(masterId, orderEntity.getId(), count);
            if (insertIgnore == 0) {
                log.error("配料量记录, 忽略产出, 组合单号: {}, 累计数: {}, 已存在", orderNo, count);
                masterId = null;
            }
            return masterId;
        }

        /**
         * 查询触发节点对应的料仓料品对照表
         *
         * @return 料仓料品对照表表
         */
        private List<SiloRawMaterialMapEntity> selectSiloRawMaterialMap() {
            LambdaQueryWrapper<SiloRawMaterialMapEntity> queryWrapper = new LambdaQueryWrapper<SiloRawMaterialMapEntity>()
                    .eq(SiloRawMaterialMapEntity::getWorkshop, workshopCH);
            return siloRawMaterialMapMapper.selectList(queryWrapper);
        }

        /**
         * 读取触发节点对应的BCRCP节点
         *
         * @param siloList 料仓列表
         * @return BCRCP节点值列表
         */
        private List<Double> readBCRCP(List<String> siloList) {
            List<NodeId> nodeIdList = siloList.stream().map(silo -> {
                String nodeIdentifier = workshop + "_" + machine + "_" + screw + "_" + T.get(silo);
                return new NodeId(1, nodeIdentifier);
            }).toList();
            try {
                List<DataValue> dataValueList = OpcUaUtil.readValue(opcUaClient, nodeIdList);
                return dataValueList.stream().map(dataValue -> Double.valueOf(String.valueOf(dataValue.getValue().getValue()))).toList();
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                log.error("读取BCRCP失败, 节点id: {}", nodeIdList, e);
                return Collections.nCopies(60, null);
            }
        }

    }

}
